C 支持许多执行标准 I/O 和文件 I/O 的函数。在本节中,我们将讨论一些最常用的 C 语言 I/O 接口。

2.8.1.标准输入/输出

每个正在运行的程序都以三个默认 I/O 流开始:标准输出 (stdout)、标准输入 (stdin) 和标准错误 (stderr)。程序可以将输出写入(打印)到 和stdoutstderr并且可以从 stdin 读取输入值。stdin 通常定义为从键盘读取输入,而 stdoutstderr 输出到终端。

C标准库stdio.h提供了printf用于打印到标准输出的函数以及scanf可用于从标准输入读入值的函数。C 还具有一次读写一个字符的函数 (getcharputchar),以及其他用于向标准 I/O 流读取和写入字符的函数和库。 一个 C 程序必须显式包含stdio.h调用这些函数。

您可以更改正在运行的程序的读取或写入的stdin stdoutstderr 的位置。实现此目的的一种方法是重定向其中一个或全部以读取或写入文件。以下是一些示例 shell 命令,用于将程序的stdinstdoutstderr 重定向到文件($是 shell 提示符):

#  redirect a.out's stdin to read from file infile.txt:
$ ./a.out < infile.txt

#  redirect a.out's stdout to print to file outfile.txt:
$ ./a.out > outfile.txt

# redirect a.out's stdout and stderr to a file out.txt
$ ./a.out &> outfile.txt

# redirect all three to different files:
#   (< redirects stdin, 1> stdout, and 2> stderr):
$ ./a.out < infile.txt 1> outfile.txt 2> errorfile.txt

printf

C 程序的printf函数类似于 Python 中的print格式化调用,其中调用者指定要打印的格式字符串。格式字符串通常包含特殊格式说明符,包括将打印制表符 (\t) 或换行符 (\n) 的特殊字符,或者为输出中的值指定占位符(% 后跟类型说明符)。在传递给 printf 的格式字符串中添加占位符时,请将其相应的值作为格式字符串后面的附加参数传递。以下是一些printf 调用示例:

printf.c

int x = 5, y = 10;
float pi = 3.14;

printf("x is %d and y is %d\n", x, y);

printf("%g \t %s \t %d\n", pi, "hello", y);

运行时,这些printf语句输出:

x is 5 and y is 10
3.14 	 hello 	 10

请注意制表符 (\t) 在第二次调用中如何打印,以及不同类型值的不同格式占位符(%g%s%d)。

下面是一组常见 C 类型的格式化占位符。请注意,long和值的占位符long long包含lorll前缀。

%f, %g: placeholders for a float or double value
%d:     placeholder for a decimal value (char, short, int)
%u:     placeholder for an unsigned decimal
%c:     placeholder for a single character
%s:     placeholder for a string value
%p:     placeholder to print an address value

%ld:    placeholder for a long value
%lu:    placeholder for an unsigned long value
%lld:   placeholder for a long long value
%llu:   placeholder for an unsigned long long  value

以下是它们的一些使用示例:

float labs;
int midterm;

labs = 93.8;
midterm = 87;

printf("Hello %s, here are your grades so far:\n", "Tanya");
printf("\t midterm: %d (out of %d)\n", midterm, 100);
printf("\t lab ave: %f\n", labs);
printf("\t final report: %c\n", 'A');

运行时,输出将如下所示:

Hello Tanya, here are your grades so far:
	 midterm: 87 (out of 100)
	 lab ave: 93.800003
	 final report: A

C 还允许您使用格式占位符指定字段宽度。这里有些例子:

%5.3f: print float value in space 5 chars wide, with 3 places beyond decimal
%20s:  print the string value in a field of 20 chars wide, right justified
%-20s: print the string value in a field of 20 chars wide, left justified
%8d:   print the int value in a field of 8 chars wide, right justified
%-8d:  print the int value in a field of 8 chars wide, left justified

这是一个更大的示例,在格式字符串中使用带有占位符的字段宽度说明符:

printf_format.c

#include <stdio.h> // library needed for printf

int main(void) {
    float x, y;
    char ch;

    x = 4.50001;
    y = 5.199999;
    ch = 'a';      // ch stores ASCII value of 'a' (the value 97)

    // .1: print x and y with single precision
    printf("%.1f %.1f\n", x, y);

    printf("%6.1f \t %6.1f \t %c\n", x, y, ch);

    // ch+1 is 98, the ASCII value of 'b'
    printf("%6.1f \t %6.1f \t %c\n", x+1, y+1, ch+1);

    printf("%6.1f \t %6.1f \t %c\n", x*20, y*20, ch+2);
    return 0;
}

运行时,程序输出如下所示:

4.5 5.2
   4.5 	    5.2 	 a
   5.5 	    6.2 	 b
  90.0 	  104.0 	 c

请注意最后三个语句中制表符和字段宽度的使用如何printf 产生表格输出。

最后,C 定义了用于以不同表示形式显示值的占位符:

%x:     print value in hexadecimal (base 16)
%o:     print value in octal (base 8)
%d:     print value in signed decimal  (base 10)
%u:     print value in unsigned decimal (unsigned base 10)
%e:     print float or double in scientific notation
(there is no formatting option to display a value in binary)

以下是使用占位符以不同表示形式打印值的示例:

int x;
char ch;

x = 26;
ch = 'A';

printf("x is %d in decimal, %x in hexadecimal and %o in octal\n", x, x, x);
printf("ch value is %d which is the ASCII value of  %c\n", ch, ch);

运行时,程序输出如下所示:

x is 26 in decimal, 1a in hexadecimal and 32 in octal
ch value is 65 which is the ASCII value of  A

scanf

scanf函数提供了一种从stdin (通常是用户通过键盘输入的值)读取值并将其存储在程序变量中的方法。该scanf函数对用户输入数据的确切格式有点挑剔,这可能使其对用户输入的格式错误敏感。

该函数的参数scanfprintf 的参数类似: scanf采用格式字符串,指定要读入的输入值的数量和类型,后跟应存储这些值的程序变量的 位置 。程序通常将 取地址 运算符(&) 与变量名称组合起来,以生成变量在程序内存中的位置 — 变量的内存地址。以下是scanf读取两个值(一个int和一个float)的调用示例:

scanf_ex.c

int x;
float pi;

// read in an int value followed by a float value ("%d%g")
// store the int value at the memory location of x (&x)
// store the float value at the memory location of pi (&pi)
scanf("%d%g", &x, &pi);

各个输入值必须由至少一个空白字符(例如空格、制表符、换行符)分隔。但是,scanf在查找每个数字文字值的开头和结尾时,会跳过前导和尾随空白字符。因此,用户可以输入值 8 和 3.14,并在这两个值之前或之后输入任意数量的空格(并且之间至少有一个或多个空格字符),并且scanf将始终读取 8 并将其分配给 x ,3.14 并读取并将其分配给 pi 。例如,两个值之间有大量空格的输入将导致读取 8 并将其存储在x、 和 3.14 中并存储在pi

       8                   3.14

程序员经常编写scanf仅由占位符说明符组成的格式字符串,中间不包含任何其他字符。为了读取上面的两个数字,格式字符串可能如下所示:

// read in an int and a float separated by at least one white space character
scanf("%d%g",&x, &pi);

getchar and putchar

C 函数 getcharputchar 分别从 stdinstdout 读取或写入单个字符值。 getchar 在需要支持仔细的错误检测和处理格式错误的用户输入的 C 程序中特别有用(scanf 这种方式并不健壮)。

ch = getchar();  // read in the next char value from stdin
putchar(ch);     // write the value of ch to stdout

2.8.2.文件输入/输出

C 标准 I/O 库 ( stdio.h) 包括用于文件 I/O 的流接口。文件存储持久数据:在创建它的程序执行之后仍然存在的数据。文本文件代表字符流,每个打开的文件都会跟踪其在字符流中的当前位置。打开文件时,当前位置从文件中的第一个字符开始,并且读(或写)随着文件中的每个字符而移动。要读取文件中的第 10 个字符,需要首先读取前 9 个字符(或者必须使用该fseek函数将当前位置显式移动到第 10 个字符)。

C 的文件接口将文件视为输入或输出流,库函数读取或写入文件流中的下一个位置。fprintf 和 fscanf 函数充当 printf 和 scanf 文件 I/O 的对应项。它们使用格式字符串来指定要写入或读取的内容,并且它们包含为写入或读取的数据提供值或存储的参数。类似地,该库提供了fputcfgetcfputsfgets 函数,用于读取或者写入单个字符或字符串到其文件流。虽然C语言中有很多支持文件I/O的库,但我们仅详细介绍 stdio.h 库中文本文件的流接口。

文本文件可能包含特殊字符,如stdinstdout流:换行符 ( '\n')、水平制表符 ( '\t') 等。此外,到达文件数据末尾时,C 的 I/O 库会生成一个特殊的文件结束符 ( EOF),表示文件末尾。从文件读取的函数可以进行EOF测试确定它们何时到达文件流的末尾。

2.8.3.在 C 中使用文本文件

要在 C 中读取或写入文件,请按照下列步骤操作:

  1. 声明 一个FILE *变量:

    FILE *infile;
    FILE *outfile;
    

    这些声明创建指向库定义FILE * 类型的指针变量。这些指针不能在应用程序中取消引用。相反,它们在传递给 I/O 库函数时引用特定的文件流。

  2. 打开 文件:通过调用 fopen 将变量与实际文件流关联起来。打开文件时,模式 参数决定程序是否以读取 ( "r")、写入 ( "w") 或追加 ( "a") 方式打开文件:

    infile = fopen("input.txt", "r");  // relative path name of file, read mode
    if (infile == NULL) {
        printf("Error: unable to open file %s\n", "input.txt");
        exit(1);
    }
    
    // fopen with absolute path name of file, write mode
    outfile = fopen("/home/me/output.txt", "w");
    if (outfile == NULL) {
        printf("Error: unable to open outfile\n");
        exit(1);
    }
    

    fopen函数返回 NULL 来报告错误,如果给定的文件名无效或用户没有打开指定文件的权限(例如,没有对该output.txt 文件的写入权限),则可能会发生这种情况。

  3. 使用 I/O 操作来读取、写入或移动文件中的当前位置:

    int ch;  // EOF is not a char value, but is an int.
             // since all char values can be stored in int, use int for ch
    
    ch = getc(infile);      // read next char from the infile stream
    if (ch != EOF) {
        putc(ch, outfile);  // write char value to the outfile stream
    }
    
  4. 关闭 文件:当程序不再需要该文件时用 fclose 关闭该文件:

    fclose(infile);
    fclose(outfile);
    

stdio库还提供了更改文件中当前位置的函数:

// to reset current position to beginning of file
void rewind(FILE *f);

rewind(infile);

// to move to a specific location in the file:
fseek(FILE *f, long offset, int whence);

fseek(f, 0, SEEK_SET);    // seek to the beginning of the file
fseek(f, 3, SEEK_CUR);    // seek 3 chars forward from the current position
fseek(f, -3, SEEK_END);   // seek 3 chars back from the end of the file

2.8.4.stdio.h文件中的标准和文件 I/O 函数

C程序 stdio.h 库具有许多用于读取和写入文件以及标准类文件流(stdinstdoutstderr)的函数。这些函数可以分为基于字符、基于字符串和格式化 I/O 函数。简而言之,以下是有关这些函数子集的一些其他详细信息:

// ---------------
// Character Based
// ---------------

// returns the next character in the file stream (EOF is an int value)
int fgetc(FILE *f);

// writes the char value c to the file stream f
// returns the char value written
int fputc(int c, FILE *f);

// pushes the character c back onto the file stream
// at most one char (and not EOF) can be pushed back
int ungetc(int c, FILE *f);

// like fgetc and fputc but for stdin and stdout
int getchar();
int putchar(int c);

// -------------
// String  Based
// -------------

// reads at most n-1 characters into the array s stopping if a newline is
// encountered, newline is included in the array which is '\0' terminated
char *fgets(char *s, int n, FILE *f);

// writes the string s (make sure '\0' terminated) to the file stream f
int fputs(char *s, FILE *f);

// ---------
// Formatted
// ---------

// writes the contents of the format string to file stream f
//   (with placeholders filled in with subsequent argument values)
// returns the number of characters printed
int fprintf(FILE *f, char *format, ...);

// like fprintf but to stdout
int printf(char *format, ...);

// use fprintf to print stderr:
fprintf(stderr, "Error return value: %d\n", ret);

// read values specified in the format string from file stream f
//   store the read-in values to program storage locations of types
//   matching the format string
// returns number of input items converted and assigned
//   or EOF on error or if EOF was reached
int fscanf(FILE *f, char *format, ...);

// like fscanf but reads from stdin
int scanf(char *format, ...);

一般来说,scanffscanf对格式错误的输入很敏感。然而,对于文件 I/O,程序员通常可以假设输入文件格式良好,因此 fscanf 在这种情况下可能足够健壮。使用 scanf 时,格式错误的用户输入通常会导致程序崩溃。一次读入一个字符并在将值转换为不同类型之前包含测试值的代码更加可靠,但它要求程序员实现更复杂的 I/O 功能。

fscanf 的格式字符串可以包含以下语法,指定不同类型的值以及从文件流读取的方式:

%d integer
%f float
%lf double
%c character
%s string, up to first white space

%[...] string, up to first character not in brackets
%[0123456789] would read in digits
%[^...] string, up to first character in brackets
%[^\n] would read everything up to a newline

获得fscanf正确的格式字符串可能很棘手,特别是在从文件中读取混合数字和字符串或字符类型时。

下面是一些使用不同格式字符串调用fscanf(和 fprintf ) 的示例(假设 fopen 上面的调用已成功执行):

int x;
double d;
char c, array[MAX];

// write int & char values to file separated by colon with newline at the end
fprintf(outfile, "%d:%c\n", x, c);

// read an int & char from file where int and char are separated by a comma
fscanf(infile, "%d,%c", &x, &c);

// read a string from a file into array (stops reading at whitespace char)
fscanf(infile,"%s", array);

// read a double and a string up to 24 chars from infile
fscanf(infile, "%lf %24s", &d, array);

// read in a string consisting of only char values in the specified set (0-5)
// stops reading when...
//   20 chars have been read OR
//   a character not in the set is reached OR
//   the file stream reaches end-of-file (EOF)
fscanf(infile, "%20[012345]", array);

// read in a string; stop when reaching a punctuation mark from the set
fscanf(infile, "%[^.,:!;]", array);

// read in two integer values: store first in long, second in int
// then read in a char value following the int value
fscanf(infile, "%ld %d%c", &x, &b, &c);

在上面的最后一个示例中,格式字符串显式读取数字后面的字符值,以确保文件流的当前位置对于任何后续调用 fscanf 都正确前进。例如,此模式通常用于显式读入(并丢弃)空白字符(如“\n”),以确保下一次调用fscanf从文件中的下一行开始。如果 下一次 调用fscanf尝试读入字符值,则需要读取附加的字符 。否则,在没有消耗换行符的情况下,下一次调用fscanf将读取换行符而不是预期的字符。如果下一次调用读取数字类型值,则前导空白字符将被 fscanf 自动丢弃,并且程序员不需要显式地从文件流中读取\n该字符。